//
//  arbor.js - version 0.91
//  a graph vizualization toolkit
//
//  Copyright (c) 2011 Samizdat Drafting Co.
//  Physics code derived from springy.js, copyright (c) 2010 Dennis Hotson
// 
//  Permission is hereby granted, free of charge, to any person
//  obtaining a copy of this software and associated documentation
//  files (the "Software"), to deal in the Software without
//  restriction, including without limitation the rights to use,
//  copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the
//  Software is furnished to do so, subject to the following
//  conditions:
// 
//  The above copyright notice and this permission notice shall be
//  included in all copies or substantial portions of the Software.
// 
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
//  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
//  OTHER DEALINGS IN THE SOFTWARE.
//

(function ($) {

    /*        etc.js */
    var trace = function (msg) {
        if (typeof (window) == "undefined" || !window.console) {
            return
        }
        var len = arguments.length;
        var args = [];
        for (var i = 0; i < len; i++) {
            args.push("arguments[" + i + "]")
        }
        eval("console.log(" + args.join(",") + ")")
    };
    var dirname = function (a) {
        var b = a.replace(/^\/?(.*?)\/?$/, "$1").split("/");
        b.pop();
        return "/" + b.join("/")
    };
    var basename = function (b) {
        var c = b.replace(/^\/?(.*?)\/?$/, "$1").split("/");
        var a = c.pop();
        if (a == "") {
            return null
        } else {
            return a
        }
    };
    var _ordinalize_re = /(\d)(?=(\d\d\d)+(?!\d))/g;
    var ordinalize = function (a) {
        var b = "" + a;
        if (a < 11000) {
            b = ("" + a).replace(_ordinalize_re, "$1,")
        } else {
            if (a < 1000000) {
                b = Math.floor(a / 1000) + "k"
            } else {
                if (a < 1000000000) {
                    b = ("" + Math.floor(a / 1000)).replace(_ordinalize_re, "$1,") + "m"
                }
            }
        }
        return b
    };
    var nano = function (a, b) {
        return a.replace(/\{([\w\-\.]*)}/g, function (f, c) {
            var d = c.split("."), e = b[d.shift()];
            $.each(d, function () {
                if (e.hasOwnProperty(this)) {
                    e = e[this]
                } else {
                    e = f
                }
            });
            return e
        })
    };
    var objcopy = function (a) {
        if (a === undefined) {
            return undefined
        }
        if (a === null) {
            return null
        }
        if (a.parentNode) {
            return a
        }
        switch (typeof a) {
            case"string":
                return a.substring(0);
                break;
            case"number":
                return a + 0;
                break;
            case"boolean":
                return a === true;
                break
        }
        var b = ($.isArray(a)) ? [] : {};
        $.each(a, function (d, c) {
            b[d] = objcopy(c)
        });
        return b
    };
    var objmerge = function (d, b) {
        d = d || {};
        b = b || {};
        var c = objcopy(d);
        for (var a in b) {
            c[a] = b[a]
        }
        return c
    };
    var objcmp = function (e, c, d) {
        if (!e || !c) {
            return e === c
        }
        if (typeof e != typeof c) {
            return false
        }
        if (typeof e != "object") {
            return e === c
        } else {
            if ($.isArray(e)) {
                if (!($.isArray(c))) {
                    return false
                }
                if (e.length != c.length) {
                    return false
                }
            } else {
                var h = [];
                for (var f in e) {
                    if (e.hasOwnProperty(f)) {
                        h.push(f)
                    }
                }
                var g = [];
                for (var f in c) {
                    if (c.hasOwnProperty(f)) {
                        g.push(f)
                    }
                }
                if (!d) {
                    h.sort();
                    g.sort()
                }
                if (h.join(",") !== g.join(",")) {
                    return false
                }
            }
            var i = true;
            $.each(e, function (a) {
                var b = objcmp(e[a], c[a]);
                i = i && b;
                if (!i) {
                    return false
                }
            });
            return i
        }
    };
    var objkeys = function (b) {
        var a = [];
        $.each(b, function (d, c) {
            if (b.hasOwnProperty(d)) {
                a.push(d)
            }
        });
        return a
    };
    var objcontains = function (c) {
        if (!c || typeof c != "object") {
            return false
        }
        for (var b = 1, a = arguments.length; b < a; b++) {
            if (c.hasOwnProperty(arguments[b])) {
                return true
            }
        }
        return false
    };
    var uniq = function (b) {
        var a = b.length;
        var d = {};
        for (var c = 0; c < a; c++) {
            d[b[c]] = true
        }
        return objkeys(d)
    };
    var arbor_path = function () {
        var a = $("script").map(function (b) {
            var c = $(this).attr("src");
            if (!c) {
                return
            }
            if (c.match(/arbor[^\/\.]*.js|dev.js/)) {
                return c.match(/.*\//) || "/"
            }
        });
        if (a.length > 0) {
            return a[0]
        } else {
            return null
        }
    };
    /*     kernel.js */
    var Kernel = function (b) {
        var k = window.location.protocol == "file:" && navigator.userAgent.toLowerCase().indexOf("chrome") > -1;
        var a = (window.Worker !== undefined && !k);
        var i = null;
        var c = null;
        var f = [];
        f.last = new Date();
        var l = null;
        var e = null;
        var d = null;
        var h = null;
        var g = false;
        var j = {
            system: b,
            tween: null,
            nodes: {},
            init: function () {
                if (typeof (Tween) != "undefined") {
                    c = Tween()
                } else {
                    if (typeof (arbor.Tween) != "undefined") {
                        c = arbor.Tween()
                    } else {
                        c = {
                            busy: function () {
                                return false
                            }, tick: function () {
                                return true
                            }, to: function () {
                                trace("Please include arbor-tween.js to enable tweens");
                                c.to = function () {
                                };
                                return
                            }
                        }
                    }
                }
                j.tween = c;
                var m = b.parameters();
                if (a) {
                    trace("using web workers");
                    l = setInterval(j.screenUpdate, m.timeout);
                    i = new Worker(arbor_path() + "arbor.js");
                    i.onmessage = j.workerMsg;
                    i.onerror = function (n) {
                        trace("physics:", n)
                    };
                    i.postMessage({type: "physics", physics: objmerge(m, {timeout: Math.ceil(m.timeout)})})
                } else {
                    trace("couldn't use web workers, be careful...");
                    i = Physics(m.dt, m.stiffness, m.repulsion, m.friction, j.system._updateGeometry);
                    j.start()
                }
                return j
            },
            graphChanged: function (m) {
                if (a) {
                    i.postMessage({type: "changes", changes: m})
                } else {
                    i._update(m)
                }
                j.start()
            },
            particleModified: function (n, m) {
                if (a) {
                    i.postMessage({type: "modify", id: n, mods: m})
                } else {
                    i.modifyNode(n, m)
                }
                j.start()
            },
            physicsModified: function (m) {
                if (!isNaN(m.timeout)) {
                    if (a) {
                        clearInterval(l);
                        l = setInterval(j.screenUpdate, m.timeout)
                    } else {
                        clearInterval(d);
                        d = null
                    }
                }
                if (a) {
                    i.postMessage({type: "sys", param: m})
                } else {
                    i.modifyPhysics(m)
                }
                j.start()
            },
            workerMsg: function (n) {
                var m = n.data.type;
                if (m == "geometry") {
                    j.workerUpdate(n.data)
                } else {
                    trace("physics:", n.data)
                }
            },
            _lastPositions: null,
            workerUpdate: function (m) {
                j._lastPositions = m;
                j._lastBounds = m.bounds
            },
            _lastFrametime: new Date().valueOf(),
            _lastBounds: null,
            _currentRenderer: null,
            screenUpdate: function () {
                var n = new Date().valueOf();
                var m = false;
                if (j._lastPositions !== null) {
                    j.system._updateGeometry(j._lastPositions);
                    j._lastPositions = null;
                    m = true
                }
                if (c && c.busy()) {
                    m = true
                }
                if (j.system._updateBounds(j._lastBounds)) {
                    m = true
                }
                if (m) {
                    var o = j.system.renderer;
                    if (o !== undefined) {
                        if (o !== e) {
                            o.init(j.system);
                            e = o
                        }
                        if (c) {
                            c.tick()
                        }
                        o.redraw();
                        var p = f.last;
                        f.last = new Date();
                        f.push(f.last - p);
                        if (f.length > 50) {
                            f.shift()
                        }
                    }
                }
            },
            physicsUpdate: function () {
                if (c) {
                    c.tick()
                }
                i.tick();
                var n = j.system._updateBounds();
                if (c && c.busy()) {
                    n = true
                }
                var o = j.system.renderer;
                var m = new Date();
                var o = j.system.renderer;
                if (o !== undefined) {
                    if (o !== e) {
                        o.init(j.system);
                        e = o
                    }
                    o.redraw({timestamp: m})
                }
                var q = f.last;
                f.last = m;
                f.push(f.last - q);
                if (f.length > 50) {
                    f.shift()
                }
                var p = i.systemEnergy();
                if ((p.mean + p.max) / 2 < 0.05) {
                    if (h === null) {
                        h = new Date().valueOf()
                    }
                    if (new Date().valueOf() - h > 1000) {
                        clearInterval(d);
                        d = null
                    } else {
                    }
                } else {
                    h = null
                }
            },
            fps: function (n) {
                if (n !== undefined) {
                    var q = 1000 / Math.max(1, targetFps);
                    j.physicsModified({timeout: q})
                }
                var r = 0;
                for (var p = 0, o = f.length; p < o; p++) {
                    r += f[p]
                }
                var m = r / Math.max(1, f.length);
                if (!isNaN(m)) {
                    return Math.round(1000 / m)
                } else {
                    return 0
                }
            },
            start: function (m) {
                if (d !== null) {
                    return
                }
                if (g && !m) {
                    return
                }
                g = false;
                if (a) {
                    i.postMessage({type: "start"})
                } else {
                    h = null;
                    d = setInterval(j.physicsUpdate, j.system.parameters().timeout)
                }
            },
            stop: function () {
                g = true;
                if (a) {
                    i.postMessage({type: "stop"})
                } else {
                    if (d !== null) {
                        clearInterval(d);
                        d = null
                    }
                }
            }
        };
        return j.init()
    };
    /*      atoms.js */
    var Node = function (a) {
        this._id = _nextNodeId++;
        this.data = a || {};
        this._mass = (a.mass !== undefined) ? a.mass : 1;
        this._fixed = (a.fixed === true) ? true : false;
        this._p = new Point((typeof (a.x) == "number") ? a.x : null, (typeof (a.y) == "number") ? a.y : null);
        delete this.data.x;
        delete this.data.y;
        delete this.data.mass;
        delete this.data.fixed
    };
    var _nextNodeId = 1;
    var Edge = function (b, c, a) {
        this._id = _nextEdgeId--;
        this.source = b;
        this.target = c;
        this.length = (a.length !== undefined) ? a.length : 1;
        this.data = (a !== undefined) ? a : {};
        delete this.data.length
    };
    var _nextEdgeId = -1;
    var Particle = function (a, b) {
        this.p = a;
        this.m = b;
        this.v = new Point(0, 0);
        this.f = new Point(0, 0)
    };
    Particle.prototype.applyForce = function (a) {
        this.f = this.f.add(a.divide(this.m))
    };
    var Spring = function (c, b, d, a) {
        this.point1 = c;
        this.point2 = b;
        this.length = d;
        this.k = a
    };
    Spring.prototype.distanceToParticle = function (a) {
        var c = that.point2.p.subtract(that.point1.p).normalize().normal();
        var b = a.p.subtract(that.point1.p);
        return Math.abs(b.x * c.x + b.y * c.y)
    };
    var Point = function (a, b) {
        if (a && a.hasOwnProperty("y")) {
            b = a.y;
            a = a.x
        }
        this.x = a;
        this.y = b
    };
    Point.random = function (a) {
        a = (a !== undefined) ? a : 5;
        return new Point(2 * a * (Math.random() - 0.5), 2 * a * (Math.random() - 0.5))
    };
    Point.prototype = {
        exploded: function () {
            return (isNaN(this.x) || isNaN(this.y))
        }, add: function (a) {
            return new Point(this.x + a.x, this.y + a.y)
        }, subtract: function (a) {
            return new Point(this.x - a.x, this.y - a.y)
        }, multiply: function (a) {
            return new Point(this.x * a, this.y * a)
        }, divide: function (a) {
            return new Point(this.x / a, this.y / a)
        }, magnitude: function () {
            return Math.sqrt(this.x * this.x + this.y * this.y)
        }, normal: function () {
            return new Point(-this.y, this.x)
        }, normalize: function () {
            return this.divide(this.magnitude())
        }
    };
    /*     system.js */
    var ParticleSystem = function (d, p, e, f, t, l, q) {
        var j = [];
        var h = null;
        var k = 0;
        var u = null;
        var m = 0.04;
        var i = [20, 20, 20, 20];
        var n = null;
        var o = null;
        if (typeof p == "object") {
            var s = p;
            e = s.friction;
            d = s.repulsion;
            t = s.fps;
            l = s.dt;
            p = s.stiffness;
            f = s.gravity;
            q = s.precision
        }
        e = isNaN(e) ? 0.5 : e;
        d = isNaN(d) ? 1000 : d;
        t = isNaN(t) ? 55 : t;
        p = isNaN(p) ? 600 : p;
        l = isNaN(l) ? 0.02 : l;
        q = isNaN(q) ? 0.6 : q;
        f = (f === true);
        var r = (t !== undefined) ? 1000 / t : 1000 / 50;
        var b = {repulsion: d, stiffness: p, friction: e, dt: l, gravity: f, precision: q, timeout: r};
        var a;
        var c = {renderer: null, tween: null, nodes: {}, edges: {}, adjacency: {}, names: {}, kernel: null};
        var g = {
            parameters: function (v) {
                if (v !== undefined) {
                    if (!isNaN(v.precision)) {
                        v.precision = Math.max(0, Math.min(1, v.precision))
                    }
                    $.each(b, function (x, w) {
                        if (v[x] !== undefined) {
                            b[x] = v[x]
                        }
                    });
                    c.kernel.physicsModified(v)
                }
                return b
            }, fps: function (v) {
                if (v === undefined) {
                    return c.kernel.fps()
                } else {
                    g.parameters({timeout: 1000 / (v || 50)})
                }
            }, start: function () {
                c.kernel.start()
            }, stop: function () {
                c.kernel.stop()
            }, addNode: function (w, B) {
                B = B || {};
                var C = c.names[w];
                if (C) {
                    C.data = B;
                    return C
                } else {
                    if (w != undefined) {
                        var v = (B.x != undefined) ? B.x : null;
                        var D = (B.y != undefined) ? B.y : null;
                        var A = (B.fixed) ? 1 : 0;
                        var z = new Node(B);
                        z.name = w;
                        c.names[w] = z;
                        c.nodes[z._id] = z;
                        j.push({t: "addNode", id: z._id, m: z.mass, x: v, y: D, f: A});
                        g._notify();
                        return z
                    }
                }
            }, pruneNode: function (w) {
                var v = g.getNode(w);
                if (typeof (c.nodes[v._id]) !== "undefined") {
                    delete c.nodes[v._id];
                    delete c.names[v.name]
                }
                $.each(c.edges, function (y, x) {
                    if (x.source._id === v._id || x.target._id === v._id) {
                        g.pruneEdge(x)
                    }
                });
                j.push({t: "dropNode", id: v._id});
                g._notify()
            }, getNode: function (v) {
                if (v._id !== undefined) {
                    return v
                } else {
                    if (typeof v == "string" || typeof v == "number") {
                        return c.names[v]
                    }
                }
            }, eachNode: function (v) {
                $.each(c.nodes, function (y, x) {
                    if (x._p.x == null || x._p.y == null) {
                        return
                    }
                    var w = (u !== null) ? g.toScreen(x._p) : x._p;
                    v.call(g, x, w)
                })
            }, addEdge: function (z, A, y) {
                z = g.getNode(z) || g.addNode(z);
                A = g.getNode(A) || g.addNode(A);
                y = y || {};
                var x = new Edge(z, A, y);
                var B = z._id;
                var C = A._id;
                c.adjacency[B] = c.adjacency[B] || {};
                c.adjacency[B][C] = c.adjacency[B][C] || [];
                var w = (c.adjacency[B][C].length > 0);
                if (w) {
                    $.extend(c.adjacency[B][C].data, x.data);
                    return
                } else {
                    c.edges[x._id] = x;
                    c.adjacency[B][C].push(x);
                    var v = (x.length !== undefined) ? x.length : 1;
                    j.push({t: "addSpring", id: x._id, fm: B, to: C, l: v});
                    g._notify()
                }
                return x
            }, pruneEdge: function (A) {
                j.push({t: "dropSpring", id: A._id});
                delete c.edges[A._id];
                for (var v in c.adjacency) {
                    for (var B in c.adjacency[v]) {
                        var w = c.adjacency[v][B];
                        for (var z = w.length - 1; z >= 0; z--) {
                            if (c.adjacency[v][B][z]._id === A._id) {
                                c.adjacency[v][B].splice(z, 1)
                            }
                        }
                    }
                }
                g._notify()
            }, getEdges: function (w, v) {
                w = g.getNode(w);
                v = g.getNode(v);
                if (!w || !v) {
                    return []
                }
                if (typeof (c.adjacency[w._id]) !== "undefined" && typeof (c.adjacency[w._id][v._id]) !== "undefined") {
                    return c.adjacency[w._id][v._id]
                }
                return []
            }, getEdgesFrom: function (v) {
                v = g.getNode(v);
                if (!v) {
                    return []
                }
                if (typeof (c.adjacency[v._id]) !== "undefined") {
                    var w = [];
                    $.each(c.adjacency[v._id], function (y, x) {
                        w = w.concat(x)
                    });
                    return w
                }
                return []
            }, getEdgesTo: function (v) {
                v = g.getNode(v);
                if (!v) {
                    return []
                }
                var w = [];
                $.each(c.edges, function (y, x) {
                    if (x.target == v) {
                        w.push(x)
                    }
                });
                return w
            }, eachEdge: function (v) {
                $.each(c.edges, function (z, x) {
                    var y = c.nodes[x.source._id]._p;
                    var w = c.nodes[x.target._id]._p;
                    if (y.x == null || w.x == null) {
                        return
                    }
                    y = (u !== null) ? g.toScreen(y) : y;
                    w = (u !== null) ? g.toScreen(w) : w;
                    if (y && w) {
                        v.call(g, x, y, w)
                    }
                })
            }, prune: function (w) {
                var v = {dropped: {nodes: [], edges: []}};
                if (w === undefined) {
                    $.each(c.nodes, function (y, x) {
                        v.dropped.nodes.push(x);
                        g.pruneNode(x)
                    })
                } else {
                    g.eachNode(function (y) {
                        var x = w.call(g, y, {from: g.getEdgesFrom(y), to: g.getEdgesTo(y)});
                        if (x) {
                            v.dropped.nodes.push(y);
                            g.pruneNode(y)
                        }
                    })
                }
                return v
            }, graft: function (w) {
                var v = {added: {nodes: [], edges: []}};
                if (w.nodes) {
                    $.each(w.nodes, function (y, x) {
                        var z = g.getNode(y);
                        if (z) {
                            z.data = x
                        } else {
                            v.added.nodes.push(g.addNode(y, x))
                        }
                        c.kernel.start()
                    })
                }
                if (w.edges) {
                    $.each(w.edges, function (z, x) {
                        var y = g.getNode(z);
                        if (!y) {
                            v.added.nodes.push(g.addNode(z, {}))
                        }
                        $.each(x, function (D, A) {
                            var C = g.getNode(D);
                            if (!C) {
                                v.added.nodes.push(g.addNode(D, {}))
                            }
                            var B = g.getEdges(z, D);
                            if (B.length > 0) {
                                B[0].data = A
                            } else {
                                v.added.edges.push(g.addEdge(z, D, A))
                            }
                        })
                    })
                }
                return v
            }, merge: function (w) {
                var v = {added: {nodes: [], edges: []}, dropped: {nodes: [], edges: []}};
                $.each(c.edges, function (A, z) {
                    if ((w.edges[z.source.name] === undefined || w.edges[z.source.name][z.target.name] === undefined)) {
                        g.pruneEdge(z);
                        v.dropped.edges.push(z)
                    }
                });
                var y = g.prune(function (A, z) {
                    if (w.nodes[A.name] === undefined) {
                        v.dropped.nodes.push(A);
                        return true
                    }
                });
                var x = g.graft(w);
                v.added.nodes = v.added.nodes.concat(x.added.nodes);
                v.added.edges = v.added.edges.concat(x.added.edges);
                v.dropped.nodes = v.dropped.nodes.concat(y.dropped.nodes);
                v.dropped.edges = v.dropped.edges.concat(y.dropped.edges);
                return v
            }, tweenNode: function (y, v, x) {
                var w = g.getNode(y);
                if (w) {
                    c.tween.to(w, v, x)
                }
            }, tweenEdge: function (w, v, z, y) {
                if (y === undefined) {
                    g._tweenEdge(w, v, z)
                } else {
                    var x = g.getEdges(w, v);
                    $.each(x, function (A, B) {
                        g._tweenEdge(B, z, y)
                    })
                }
            }, _tweenEdge: function (w, v, x) {
                if (w && w._id !== undefined) {
                    c.tween.to(w, v, x)
                }
            }, _updateGeometry: function (y) {
                if (y != undefined) {
                    var v = (y.epoch < k);
                    a = y.energy;
                    var z = y.geometry;
                    if (z !== undefined) {
                        for (var x = 0, w = z.length / 3; x < w; x++) {
                            var A = z[3 * x];
                            if (v && c.nodes[A] == undefined) {
                                continue
                            }
                            c.nodes[A]._p.x = z[3 * x + 1];
                            c.nodes[A]._p.y = z[3 * x + 2]
                        }
                    }
                }
            }, screen: function (v) {
                if (v == undefined) {
                    return {size: (u) ? objcopy(u) : undefined, padding: i.concat(), step: m}
                }
                if (v.size !== undefined) {
                    g.screenSize(v.size.width, v.size.height)
                }
                if (!isNaN(v.step)) {
                    g.screenStep(v.step)
                }
                if (v.padding !== undefined) {
                    g.screenPadding(v.padding)
                }
            }, screenSize: function (v, w) {
                u = {width: v, height: w};
                g._updateBounds()
            }, screenPadding: function (y, z, v, w) {
                if ($.isArray(y)) {
                    trbl = y
                } else {
                    trbl = [y, z, v, w]
                }
                var A = trbl[0];
                var x = trbl[1];
                var B = trbl[2];
                if (x === undefined) {
                    trbl = [A, A, A, A]
                } else {
                    if (B == undefined) {
                        trbl = [A, x, A, x]
                    }
                }
                i = trbl
            }, screenStep: function (v) {
                m = v
            }, toScreen: function (x) {
                if (!n || !u) {
                    return
                }
                var w = i || [0, 0, 0, 0];
                var v = n.bottomright.subtract(n.topleft);
                var z = w[3] + x.subtract(n.topleft).divide(v.x).x * (u.width - (w[1] + w[3]));
                var y = w[0] + x.subtract(n.topleft).divide(v.y).y * (u.height - (w[0] + w[2]));
                return arbor.Point(z, y)
            }, fromScreen: function (z) {
                if (!n || !u) {
                    return
                }
                var y = i || [0, 0, 0, 0];
                var x = n.bottomright.subtract(n.topleft);
                var w = (z.x - y[3]) / (u.width - (y[1] + y[3])) * x.x + n.topleft.x;
                var v = (z.y - y[0]) / (u.height - (y[0] + y[2])) * x.y + n.topleft.y;
                return arbor.Point(w, v)
            }, _updateBounds: function (w) {
                if (u === null) {
                    return
                }
                if (w) {
                    o = w
                } else {
                    o = g.bounds()
                }
                var z = new Point(o.bottomright.x, o.bottomright.y);
                var y = new Point(o.topleft.x, o.topleft.y);
                var B = z.subtract(y);
                var v = y.add(B.divide(2));
                var x = 4;
                var D = new Point(Math.max(B.x, x), Math.max(B.y, x));
                o.topleft = v.subtract(D.divide(2));
                o.bottomright = v.add(D.divide(2));
                if (!n) {
                    if ($.isEmptyObject(c.nodes)) {
                        return false
                    }
                    n = o;
                    return true
                }
                var C = m;
                _newBounds = {
                    bottomright: n.bottomright.add(o.bottomright.subtract(n.bottomright).multiply(C)),
                    topleft: n.topleft.add(o.topleft.subtract(n.topleft).multiply(C))
                };
                var A = new Point(n.topleft.subtract(_newBounds.topleft).magnitude(), n.bottomright.subtract(_newBounds.bottomright).magnitude());
                if (A.x * u.width > 1 || A.y * u.height > 1) {
                    n = _newBounds;
                    return true
                } else {
                    return false
                }
            }, energy: function () {
                return a
            }, bounds: function () {
                var w = null;
                var v = null;
                $.each(c.nodes, function (z, y) {
                    if (!w) {
                        w = new Point(y._p);
                        v = new Point(y._p);
                        return
                    }
                    var x = y._p;
                    if (x.x === null || x.y === null) {
                        return
                    }
                    if (x.x > w.x) {
                        w.x = x.x
                    }
                    if (x.y > w.y) {
                        w.y = x.y
                    }
                    if (x.x < v.x) {
                        v.x = x.x
                    }
                    if (x.y < v.y) {
                        v.y = x.y
                    }
                });
                if (w && v) {
                    return {bottomright: w, topleft: v}
                } else {
                    return {topleft: new Point(-1, -1), bottomright: new Point(1, 1)}
                }
            }, nearest: function (x) {
                if (u !== null) {
                    x = g.fromScreen(x)
                }
                var w = {node: null, point: null, distance: null};
                var v = g;
                $.each(c.nodes, function (B, y) {
                    var z = y._p;
                    if (z.x === null || z.y === null) {
                        return
                    }
                    var A = z.subtract(x).magnitude();
                    if (w.distance === null || A < w.distance) {
                        w = {node: y, point: z, distance: A};
                        if (u !== null) {
                            w.screenPoint = g.toScreen(z)
                        }
                    }
                });
                if (w.node) {
                    if (u !== null) {
                        w.distance = g.toScreen(w.node.p).subtract(g.toScreen(x)).magnitude()
                    }
                    return w
                } else {
                    return null
                }
            }, _notify: function () {
                if (h === null) {
                    k++
                } else {
                    clearTimeout(h)
                }
                h = setTimeout(g._synchronize, 20)
            }, _synchronize: function () {
                if (j.length > 0) {
                    c.kernel.graphChanged(j);
                    j = [];
                    h = null
                }
            },
        };
        c.kernel = Kernel(g);
        c.tween = c.kernel.tween || null;
        Node.prototype.__defineGetter__("p", function () {
            var w = this;
            var v = {};
            v.__defineGetter__("x", function () {
                return w._p.x
            });
            v.__defineSetter__("x", function (x) {
                c.kernel.particleModified(w._id, {x: x})
            });
            v.__defineGetter__("y", function () {
                return w._p.y
            });
            v.__defineSetter__("y", function (x) {
                c.kernel.particleModified(w._id, {y: x})
            });
            v.__proto__ = Point.prototype;
            return v
        });
        Node.prototype.__defineSetter__("p", function (v) {
            this._p.x = v.x;
            this._p.y = v.y;
            c.kernel.particleModified(this._id, {x: v.x, y: v.y})
        });
        Node.prototype.__defineGetter__("mass", function () {
            return this._mass
        });
        Node.prototype.__defineSetter__("mass", function (v) {
            this._mass = v;
            c.kernel.particleModified(this._id, {m: v})
        });
        Node.prototype.__defineSetter__("tempMass", function (v) {
            c.kernel.particleModified(this._id, {_m: v})
        });
        Node.prototype.__defineGetter__("fixed", function () {
            return this._fixed
        });
        Node.prototype.__defineSetter__("fixed", function (v) {
            this._fixed = v;
            c.kernel.particleModified(this._id, {f: v ? 1 : 0})
        });
        return g
    };
    /* barnes-hut.js */
    var BarnesHutTree = function () {
        var b = [];
        var a = 0;
        var e = null;
        var d = 0.5;
        var c = {
            init: function (g, h, f) {
                d = f;
                a = 0;
                e = c._newBranch();
                e.origin = g;
                e.size = h.subtract(g)
            }, insert: function (j) {
                var f = e;
                var g = [j];
                while (g.length) {
                    var h = g.shift();
                    var m = h._m || h.m;
                    var p = c._whichQuad(h, f);
                    if (f[p] === undefined) {
                        f[p] = h;
                        f.mass += m;
                        if (f.p) {
                            f.p = f.p.add(h.p.multiply(m))
                        } else {
                            f.p = h.p.multiply(m)
                        }
                    } else {
                        if ("origin" in f[p]) {
                            f.mass += (m);
                            if (f.p) {
                                f.p = f.p.add(h.p.multiply(m))
                            } else {
                                f.p = h.p.multiply(m)
                            }
                            f = f[p];
                            g.unshift(h)
                        } else {
                            var l = f.size.divide(2);
                            var n = new Point(f.origin);
                            if (p[0] == "s") {
                                n.y += l.y
                            }
                            if (p[1] == "e") {
                                n.x += l.x
                            }
                            var o = f[p];
                            f[p] = c._newBranch();
                            f[p].origin = n;
                            f[p].size = l;
                            f.mass = m;
                            f.p = h.p.multiply(m);
                            f = f[p];
                            if (o.p.x === h.p.x && o.p.y === h.p.y) {
                                var k = l.x * 0.08;
                                var i = l.y * 0.08;
                                o.p.x = Math.min(n.x + l.x, Math.max(n.x, o.p.x - k / 2 + Math.random() * k));
                                o.p.y = Math.min(n.y + l.y, Math.max(n.y, o.p.y - i / 2 + Math.random() * i))
                            }
                            g.push(o);
                            g.unshift(h)
                        }
                    }
                }
            }, applyForces: function (m, g) {
                var f = [e];
                while (f.length) {
                    node = f.shift();
                    if (node === undefined) {
                        continue
                    }
                    if (m === node) {
                        continue
                    }
                    if ("f" in node) {
                        var k = m.p.subtract(node.p);
                        var l = Math.max(1, k.magnitude());
                        var i = ((k.magnitude() > 0) ? k : Point.random(1)).normalize();
                        m.applyForce(i.multiply(g * (node._m || node.m)).divide(l * l))
                    } else {
                        var j = m.p.subtract(node.p.divide(node.mass)).magnitude();
                        var h = Math.sqrt(node.size.x * node.size.y);
                        if (h / j > d) {
                            f.push(node.ne);
                            f.push(node.nw);
                            f.push(node.se);
                            f.push(node.sw)
                        } else {
                            var k = m.p.subtract(node.p.divide(node.mass));
                            var l = Math.max(1, k.magnitude());
                            var i = ((k.magnitude() > 0) ? k : Point.random(1)).normalize();
                            m.applyForce(i.multiply(g * (node.mass)).divide(l * l))
                        }
                    }
                }
            }, _whichQuad: function (i, f) {
                if (i.p.exploded()) {
                    return null
                }
                var h = i.p.subtract(f.origin);
                var g = f.size.divide(2);
                if (h.y < g.y) {
                    if (h.x < g.x) {
                        return "nw"
                    } else {
                        return "ne"
                    }
                } else {
                    if (h.x < g.x) {
                        return "sw"
                    } else {
                        return "se"
                    }
                }
            }, _newBranch: function () {
                if (b[a]) {
                    var f = b[a];
                    f.ne = f.nw = f.se = f.sw = undefined;
                    f.mass = 0;
                    delete f.p
                } else {
                    f = {origin: null, size: null, nw: undefined, ne: undefined, sw: undefined, se: undefined, mass: 0};
                    b[a] = f
                }
                a++;
                return f
            }
        };
        return c
    };
    /*    physics.js */
    var Physics = function (a, m, n, e, h) {
        var f = BarnesHutTree();
        var c = {particles: {}, springs: {}};
        var l = {particles: {}};
        var o = [];
        var k = [];
        var d = 0;
        var b = {sum: 0, max: 0, mean: 0};
        var g = {topleft: new Point(-1, -1), bottomright: new Point(1, 1)};
        var j = 1000;
        var i = {
            stiffness: (m !== undefined) ? m : 1000,
            repulsion: (n !== undefined) ? n : 600,
            friction: (e !== undefined) ? e : 0.3,
            gravity: false,
            dt: (a !== undefined) ? a : 0.02,
            theta: 0.4,
            init: function () {
                return i
            },
            modifyPhysics: function (p) {
                $.each(["stiffness", "repulsion", "friction", "gravity", "dt", "precision"], function (r, s) {
                    if (p[s] !== undefined) {
                        if (s == "precision") {
                            i.theta = 1 - p[s];
                            return
                        }
                        i[s] = p[s];
                        if (s == "stiffness") {
                            var q = p[s];
                            $.each(c.springs, function (u, t) {
                                t.k = q
                            })
                        }
                    }
                })
            },
            addNode: function (u) {
                var t = u.id;
                var q = u.m;
                var p = g.bottomright.x - g.topleft.x;
                var s = g.bottomright.y - g.topleft.y;
                var r = new Point((u.x != null) ? u.x : g.topleft.x + p * Math.random(), (u.y != null) ? u.y : g.topleft.y + s * Math.random());
                c.particles[t] = new Particle(r, q);
                c.particles[t].connections = 0;
                c.particles[t].fixed = (u.f === 1);
                l.particles[t] = c.particles[t];
                o.push(c.particles[t])
            },
            dropNode: function (s) {
                var r = s.id;
                var q = c.particles[r];
                var p = $.inArray(q, o);
                if (p > -1) {
                    o.splice(p, 1)
                }
                delete c.particles[r];
                delete l.particles[r]
            },
            modifyNode: function (r, p) {
                if (r in c.particles) {
                    var q = c.particles[r];
                    if ("x" in p) {
                        q.p.x = p.x
                    }
                    if ("y" in p) {
                        q.p.y = p.y
                    }
                    if ("m" in p) {
                        q.m = p.m
                    }
                    if ("f" in p) {
                        q.fixed = (p.f === 1)
                    }
                    if ("_m" in p) {
                        if (q._m === undefined) {
                            q._m = q.m
                        }
                        q.m = p._m
                    }
                }
            },
            addSpring: function (t) {
                var s = t.id;
                var p = t.l;
                var r = c.particles[t.fm];
                var q = c.particles[t.to];
                if (r !== undefined && q !== undefined) {
                    c.springs[s] = new Spring(r, q, p, i.stiffness);
                    k.push(c.springs[s]);
                    r.connections++;
                    q.connections++;
                    delete l.particles[t.fm];
                    delete l.particles[t.to]
                }
            },
            dropSpring: function (s) {
                var r = s.id;
                var q = c.springs[r];
                q.point1.connections--;
                q.point2.connections--;
                var p = $.inArray(q, k);
                if (p > -1) {
                    k.splice(p, 1)
                }
                delete c.springs[r]
            },
            _update: function (p) {
                d++;
                $.each(p, function (q, r) {
                    if (r.t in i) {
                        i[r.t](r)
                    }
                });
                return d
            },
            tick: function () {
                i.tendParticles();
                i.eulerIntegrator(i.dt);
                i.tock()
            },
            tock: function () {
                var p = [];
                $.each(c.particles, function (r, q) {
                    p.push(r);
                    p.push(q.p.x);
                    p.push(q.p.y)
                });
                if (h) {
                    h({geometry: p, epoch: d, energy: b, bounds: g})
                }
            },
            tendParticles: function () {
                $.each(c.particles, function (q, p) {
                    if (p._m !== undefined) {
                        if (Math.abs(p.m - p._m) < 1) {
                            p.m = p._m;
                            delete p._m
                        } else {
                            p.m *= 0.98
                        }
                    }
                    p.v.x = p.v.y = 0
                })
            },
            eulerIntegrator: function (p) {
                if (i.repulsion > 0) {
                    if (i.theta > 0) {
                        i.applyBarnesHutRepulsion()
                    } else {
                        i.applyBruteForceRepulsion()
                    }
                }
                if (i.stiffness > 0) {
                    i.applySprings()
                }
                i.applyCenterDrift();
                if (i.gravity) {
                    i.applyCenterGravity()
                }
                i.updateVelocity(p);
                i.updatePosition(p)
            },
            applyBruteForceRepulsion: function () {
                $.each(c.particles, function (q, p) {
                    $.each(c.particles, function (s, r) {
                        if (p !== r) {
                            var u = p.p.subtract(r.p);
                            var v = Math.max(1, u.magnitude());
                            var t = ((u.magnitude() > 0) ? u : Point.random(1)).normalize();
                            p.applyForce(t.multiply(i.repulsion * (r._m || r.m) * 0.5).divide(v * v * 0.5));
                            r.applyForce(t.multiply(i.repulsion * (p._m || p.m) * 0.5).divide(v * v * -0.5))
                        }
                    })
                })
            },
            applyBarnesHutRepulsion: function () {
                if (!g.topleft || !g.bottomright) {
                    return
                }
                var q = new Point(g.bottomright);
                var p = new Point(g.topleft);
                f.init(p, q, i.theta);
                $.each(c.particles, function (s, r) {
                    f.insert(r)
                });
                $.each(c.particles, function (s, r) {
                    f.applyForces(r, i.repulsion)
                })
            },
            applySprings: function () {
                $.each(c.springs, function (t, p) {
                    var s = p.point2.p.subtract(p.point1.p);
                    var q = p.length - s.magnitude();
                    var r = ((s.magnitude() > 0) ? s : Point.random(1)).normalize();
                    p.point1.applyForce(r.multiply(p.k * q * -0.5));
                    p.point2.applyForce(r.multiply(p.k * q * 0.5))
                })
            },
            applyCenterDrift: function () {
                var q = 0;
                var r = new Point(0, 0);
                $.each(c.particles, function (t, s) {
                    r.add(s.p);
                    q++
                });
                if (q == 0) {
                    return
                }
                var p = r.divide(-q);
                $.each(c.particles, function (t, s) {
                    s.applyForce(p)
                })
            },
            applyCenterGravity: function () {
                $.each(c.particles, function (r, p) {
                    var q = p.p.multiply(-1);
                    p.applyForce(q.multiply(i.repulsion / 100))
                })
            },
            updateVelocity: function (p) {
                $.each(c.particles, function (t, q) {
                    if (q.fixed) {
                        q.v = new Point(0, 0);
                        q.f = new Point(0, 0);
                        return
                    }
                    var s = q.v.magnitude();
                    q.v = q.v.add(q.f.multiply(p)).multiply(1 - i.friction);
                    q.f.x = q.f.y = 0;
                    var r = q.v.magnitude();
                    if (r > j) {
                        q.v = q.v.divide(r * r)
                    }
                })
            },
            updatePosition: function (q) {
                var r = 0, p = 0, u = 0;
                var t = null;
                var s = null;
                $.each(c.particles, function (w, v) {
                    v.p = v.p.add(v.v.multiply(q));
                    var x = v.v.magnitude();
                    var z = x * x;
                    r += z;
                    p = Math.max(z, p);
                    u++;
                    if (!t) {
                        t = new Point(v.p.x, v.p.y);
                        s = new Point(v.p.x, v.p.y);
                        return
                    }
                    var y = v.p;
                    if (y.x === null || y.y === null) {
                        return
                    }
                    if (y.x > t.x) {
                        t.x = y.x
                    }
                    if (y.y > t.y) {
                        t.y = y.y
                    }
                    if (y.x < s.x) {
                        s.x = y.x
                    }
                    if (y.y < s.y) {
                        s.y = y.y
                    }
                });
                b = {sum: r, max: p, mean: r / u, n: u};
                g = {topleft: s || new Point(-1, -1), bottomright: t || new Point(1, 1)}
            },
            systemEnergy: function (p) {
                return b
            }
        };
        return i.init()
    };
    var _nearParticle = function (b, c) {
        var c = c || 0;
        var a = b.x;
        var f = b.y;
        var e = c * 2;
        return new Point(a - c + Math.random() * e, f - c + Math.random() * e)
    };

    // if called as a worker thread, set up a run loop for the Physics object and bail out
    if (typeof (window) == 'undefined') return (function () {
        /* hermetic.js */
        $ = {
            each: function (d, e) {
                if ($.isArray(d)) {
                    for (var c = 0, b = d.length; c < b; c++) {
                        e(c, d[c])
                    }
                } else {
                    for (var a in d) {
                        e(a, d[a])
                    }
                }
            }, map: function (a, c) {
                var b = [];
                $.each(a, function (f, e) {
                    var d = c(e);
                    if (d !== undefined) {
                        b.push(d)
                    }
                });
                return b
            }, extend: function (c, b) {
                if (typeof b != "object") {
                    return c
                }
                for (var a in b) {
                    if (b.hasOwnProperty(a)) {
                        c[a] = b[a]
                    }
                }
                return c
            }, isArray: function (a) {
                if (!a) {
                    return false
                }
                return (a.constructor.toString().indexOf("Array") != -1)
            }, inArray: function (c, a) {
                for (var d = 0, b = a.length; d < b; d++) {
                    if (a[d] === c) {
                        return d
                    }
                }
                return -1
            }, isEmptyObject: function (a) {
                if (typeof a !== "object") {
                    return false
                }
                var b = true;
                $.each(a, function (c, d) {
                    b = false
                });
                return b
            },
        };
        /*     worker.js */
        var PhysicsWorker = function () {
            var b = 20;
            var a = null;
            var d = null;
            var c = null;
            var g = [];
            var f = new Date().valueOf();
            var e = {
                init: function (h) {
                    e.timeout(h.timeout);
                    a = Physics(h.dt, h.stiffness, h.repulsion, h.friction, e.tock);
                    return e
                }, timeout: function (h) {
                    if (h != b) {
                        b = h;
                        if (d !== null) {
                            e.stop();
                            e.go()
                        }
                    }
                }, go: function () {
                    if (d !== null) {
                        return
                    }
                    c = null;
                    d = setInterval(e.tick, b)
                }, stop: function () {
                    if (d === null) {
                        return
                    }
                    clearInterval(d);
                    d = null
                }, tick: function () {
                    a.tick();
                    var h = a.systemEnergy();
                    if ((h.mean + h.max) / 2 < 0.05) {
                        if (c === null) {
                            c = new Date().valueOf()
                        }
                        if (new Date().valueOf() - c > 1000) {
                            e.stop()
                        } else {
                        }
                    } else {
                        c = null
                    }
                }, tock: function (h) {
                    h.type = "geometry";
                    postMessage(h)
                }, modifyNode: function (i, h) {
                    a.modifyNode(i, h);
                    e.go()
                }, modifyPhysics: function (h) {
                    a.modifyPhysics(h)
                }, update: function (h) {
                    var i = a._update(h)
                }
            };
            return e
        };
        var physics = PhysicsWorker();
        onmessage = function (a) {
            if (!a.data.type) {
                postMessage("¿kérnèl?");
                return
            }
            if (a.data.type == "physics") {
                var b = a.data.physics;
                physics.init(a.data.physics);
                return
            }
            switch (a.data.type) {
                case"modify":
                    physics.modifyNode(a.data.id, a.data.mods);
                    break;
                case"changes":
                    physics.update(a.data.changes);
                    physics.go();
                    break;
                case"start":
                    physics.go();
                    break;
                case"stop":
                    physics.stop();
                    break;
                case"sys":
                    var b = a.data.param || {};
                    if (!isNaN(b.timeout)) {
                        physics.timeout(b.timeout)
                    }
                    physics.modifyPhysics(b);
                    physics.go();
                    break
            }
        };
    })()


    arbor = (typeof (arbor) !== 'undefined') ? arbor : {}
    $.extend(arbor, {
        // object constructors (don't use ‘new’, just call them)
        ParticleSystem: ParticleSystem,
        Point: function (x, y) {
            return new Point(x, y)
        },

        // immutable object with useful methods
        etc: {
            trace: trace,              // ƒ(msg) -> safe console logging
            dirname: dirname,          // ƒ(path) -> leading part of path
            basename: basename,        // ƒ(path) -> trailing part of path
            ordinalize: ordinalize,    // ƒ(num) -> abbrev integers (and add commas)
            objcopy: objcopy,          // ƒ(old) -> clone an object
            objcmp: objcmp,            // ƒ(a, b, strict_ordering) -> t/f comparison
            objkeys: objkeys,          // ƒ(obj) -> array of all keys in obj
            objmerge: objmerge,        // ƒ(dst, src) -> like $.extend but non-destructive
            uniq: uniq,                // ƒ(arr) -> array of unique items in arr
            arbor_path: arbor_path,    // ƒ() -> guess the directory of the lib code
        }
    })

})(this.jQuery)